package com.gitee.wsl.struct.version

/**
 * This class represents a range of versions. Each range needs at least a lower
 * bound but can as well have an upper bound. See OSGi v4 reference 3.2.5
 *
 * @author Philip Helger
 */
class VersionRange(
    val floorVersion: Version,
    val isIncludingFloor: Boolean,
    val ceilVersion: Version? = null,
    val isIncludingCeil: Boolean
) : Comparable<VersionRange> {

    /**
     * Create a new version range depicted by two versions.
     *
     * @param aFloorVersion
     * the floor version of the range - may not be null
     * @param bIncludeFloorVersion
     * if true, a &gt;= comparison is used on the version number, else a
     * &gt; comparison is used
     * @param aCeilingVersion
     * the ceiling version of the range - may be null
     * @param bIncludeCeilingVersion
     * if true, a &lt;= comparison is used on the version number, else a
     * &lt; comparison is used
     * @throws IllegalArgumentException
     * if the floor version to be used is &gt; the ceiling version or if
     * the floor version is null.
     */

    init {
        // check if floor <= ceil
        require(!(ceilVersion != null && floorVersion > ceilVersion)) { "Floor version may not be greater than the ceiling version!" }
    }

    fun versionMatches(rhs: Version): Boolean {
        // returns -1 if floor < rhs
        // -> error
        var i = floorVersion.compareTo(rhs)
        if (if (isIncludingFloor) i > 0 else i >= 0) return false

        // check ceiling version
        if (ceilVersion != null) {
            i = ceilVersion.compareTo(rhs)
            if (if (isIncludingCeil) i < 0 else i <= 0) return false
        }
        return true
    }

    /**
     * Compare this version range to another version range. Returns -1 if this is
     * &lt; than the passed version or +1 if this is &gt; the passed version range
     *
     * @param rhs
     * the version range to compare to
     * @return 0 if the passed version range is equal to this version range<br></br>
     * -1 if the floor version of this is &lt; than the floor version of
     * the passed version range.<br></br>
     * -1 if the floor versions are equal but the ceiling version of this
     * has a lower upper bound than the passed version range<br></br>
     * +1 if the floor version of this is &gt; than the floor version of
     * the passed version range.<br></br>
     * +1 if the floor versions are equal but the ceiling version of this
     * has a higher upper bound than the passed version range<br></br>
     */
    override operator fun compareTo(rhs: VersionRange): Int {
        var i = floorVersion.compareTo(rhs.floorVersion)
        if (i == 0) {
            if (isIncludingFloor && !rhs.isIncludingFloor) {
                // this < rhs
                i = -1
            } else if (!isIncludingFloor && rhs.isIncludingFloor) {
                // this > rhs
                i = +1
            }
            if (i == 0) {
                // compare ceiling
                if (ceilVersion != null && rhs.ceilVersion == null)
                    i = -1
                else if (ceilVersion == null && rhs.ceilVersion != null)
                    i = +1
                else if (ceilVersion != null && rhs.ceilVersion != null)
                    i = ceilVersion.compareTo(rhs.ceilVersion)
                // else i stays 0 if both are null
                if (i == 0) {
                    if (isIncludingCeil && !rhs.isIncludingCeil)
                        i = +1
                    else if (!isIncludingCeil && rhs.isIncludingCeil)
                        i = -1
                }
            }
        }
        return i
    }

    /**
     * Converts the version range to a string. The brackets whether floor or
     * ceiling version should be included or not is always prepended and appended.
     * If a ceiling version is present, the ceiling version is appended with a
     * single comma as a delimiter.<br></br>
     * Example return: "[1.2.3,4.5.6)"
     *
     * @return The version range in a parseable string format.
     */
    val asString: String
        get() = getAsString(Version.DEFAULT_PRINT_ZERO_ELEMENTS)

    /**
     * Converts the version range to a string. The brackets whether floor or
     * ceiling version should be included or not is always prepended and appended.
     * If a ceiling version is present, the ceiling version is appended with a
     * single comma as a delimiter.<br></br>
     * Example return: "[1.2.3,4.5.6)"
     *
     * @param bPrintZeroElements
     * If `true` than trailing zeroes are printed, otherwise
     * printed zeroes are not printed.
     * @return Never `null`.
     */

    fun getAsString(bPrintZeroElements: Boolean): String {
        // special handling if no ceiling version is present
        val aSB = StringBuilder(if (isIncludingFloor) "[" else "(")
        aSB.append(floorVersion.getAsString(bPrintZeroElements))
        if (ceilVersion != null) {
            aSB.append(',').append(ceilVersion.getAsString(bPrintZeroElements))
        }
        return aSB.append(if (isIncludingCeil) ']' else ')').toString()
    }

    override fun equals(other: Any?): Boolean {
        if (other === this) return true
        if (other == null || this::class != other::class) return false
        val rhs = other as VersionRange
        return isIncludingFloor == rhs.isIncludingFloor &&
                floorVersion == rhs.floorVersion && isIncludingCeil == rhs.isIncludingCeil &&
                ceilVersion == rhs.ceilVersion
    }

    override fun hashCode(): Int {
        var result = floorVersion.hashCode()
        result = 31 * result + isIncludingFloor.hashCode()
        result = 31 * result + (ceilVersion?.hashCode() ?: 0)
        result = 31 * result + isIncludingCeil.hashCode()
        return result
    }

    companion object {
        /**
         * Default version range string.
         */
        const val DEFAULT_VERSION_RANGE_STRING = "[0)"

        /**
         * Construct a version range object from a string.<br></br>
         * Examples:<br></br>
         *
         *  * [1.2.3, 4.5.6) -- 1.2.3 &lt;= x &lt; 4.5.6
         *  * [1.2.3, 4.5.6] -- 1.2.3 &lt;= x &lt;= 4.5.6
         *  * (1.2.3, 4.5.6) -- 1.2.3 &lt; x &lt; 4.5.6
         *  * (1.2.3, 4.5.6] -- 1.2.3 &lt; x &lt;= 4.5.6
         *  * 1.2.3 -- 1.2.3 &lt;= x
         *  * [1.2.3 -- 1.2.3 &lt;= x
         *  * (1.2.3 -- 1.2.3 &lt; x
         *  * *null* -- 0.0.0 &lt;= x
         *  * 1, 4 -- 1 &lt;= x &lt;= 4
         *
         *
         * @param sVersionString
         * the version range in a string format as depicted above
         * @return The parsed [VersionRange] object
         * @throws IllegalArgumentException
         * if the floor version is &lt; than the ceiling version
         */

        fun parse(sVersionString: String): VersionRange {
            val s = sVersionString.trim { it <= ' ' }
            if (s.isEmpty()) {
                // empty string == range [0.0, infinity)
                return VersionRange(Version.DEFAULT_VERSION, true, null, false)
            }
            val aFloorVersion: Version
            val bIncludeFloor: Boolean
            val aCeilVersion: Version?
            val bIncludeCeil: Boolean
            var i = 0
            // parse initial token
            if (s[i] == '[') {
                bIncludeFloor = true
                i++
            } else if (s[i] == '(') {
                bIncludeFloor = false
                i++
            } else bIncludeFloor = true

            // check last token
            var j = 0
            if (s.endsWith( ']')) {
                bIncludeCeil = true
                j++
            } else if (s.endsWith(')')) {
                bIncludeCeil = false
                j++
            } else bIncludeCeil = false

            // get length of version stuff
            val nRestLen = s.length - i - j
            if (nRestLen == 0) {
                // only delimiter braces present?
                aFloorVersion = Version.DEFAULT_VERSION
                aCeilVersion = null
            } else {
                val parts = s.substring(i, s.length - j).splitToSequence(',').toList()
                val sFloor = parts[0].trim { it <= ' ' }
                val sCeiling = if (parts.size > 1) parts[1].trim { it <= ' ' } else null

                // get floor version
                aFloorVersion = Version.parse(sFloor)
                aCeilVersion = if (sCeiling.isNullOrEmpty()) null else Version.parse(sCeiling)
            }

            // check if floor <= ceil
            require(!(aCeilVersion != null && aFloorVersion > aCeilVersion)) { "Floor version may not be greater than the ceiling version!" }
            return VersionRange(aFloorVersion, bIncludeFloor, aCeilVersion, bIncludeCeil)
        }
    }
}